S100 Computers

Home S-100 Boards History New Boards Software Boards For Sale
Forum Other Web Sites Quiz Index    

BRINGING UP CPM3 FOR THE FIRST TIME ON A FLOPPY DISK BASED SYSTEM
Most early S-100 Computer systems used CPM2.2 (or earlier) as the basic computer disk operating system.  The system was fairly simple to implement and was the germination base for much of the microcomputer worlds software.  The system however had many limitations. In particular it was designed for a standard 8" IBM single density single sided floppy disk with 128 byte sectors.  As newer disk sizes and formats started to appear -- particularly hard disks, the system started to show its limitations.  Digital Research's answer was CPM3.  This version of CPM allowed essentially any sector size and disk format to be used easily and efficiently.  What CPM3 did was hide within the operation system itself the 128 byte sector size requirement and allow the BIOS to work easily with any sector size "transparently". The system had an  elaborate disk hashing/data buffering system as well. 

Of particular usefulness was the fact that it existed in two forms. A "standard" NON BANKED version that operated like CPM2.2 in a Z80 system with 64K (or less) of RAM. However there was a much more efficient "BANKED" version of CPM3 which by using onboard 'bank switching" hardware, could utilize up to (in theory) many megabytes of RAM. Typically 128K or 256K RAM systems were used.   This allowed for a very fast and sophisticated system. 

It should be noted that this BANKED system absolutely requires that the Z80 can switch in and out a portion of its 64K address space with other RAM boards.  There were a number of ways this was done in hardware.  The Cromemco and Godbout systems utilized an IO port to switch RAM boards.  Intersystem's and our own S100Computes/N8VEM Z80 board, use on-board Z80 CPU board hardware to extend the addressing range of the Z80.  We will discuss this later.

For now lets start with a very simple CPM3 BIOS for a <64K System.  We will step by step build the system up to a much more complex setup. Eventually arriving at a BANKED system with multiple floppy, hard and memory disk connections.  




Writing a simple CPM3 BIOS for a ZFDC FDC Board
Before we start if would be very helpful if you first look over the very detailed description Digital Research provide in their CPM3 BIOS configuration manual. One word of warning!  This manual (CPM3 System Guide),  is extremely detailed and contains far more detail than is typically needed. It explains in great detail how the disk parameter tables are setup etc.  When you read this for the first time you may run away. Hang in there, Digital Research provided a number of assembler macro routines that do all the dirty configuration work for you. They also provided an interactive "GENCPM" program that via a series of questions and answers makes and configures a complete system for you.  Thanks to PC Pete's for the CPM3 System guide. We now also have from him beautiful copies of the Digital Research CPM3 User Guide, Programmers Guide and Command Guide.

While it is useful (and comforting), you really don't have to know about many of the underlying process in writing a CPM3 BIOS. Your job as we will see, is simply to write 5 core modules for the hardware to:-

Initialize a drive (if required)
Log in a drive
Read a sector
Write a sector.
Write a "DPH" table that describes the disk format


These modules have to be bullet proof, but that's it. The Digital Research GENCPM will do the rest.

The "Chick & Egg"  Problem.
One of the issues in building a CPM system in the early days was that you really need a running system to build a new one.  In the late 70's many of us, myself included, built our first system utilizing a cassette tape based assembler (TDL) and a primitive editor.  Alternatively you could go to a friends house and modify his BIOS for your hardware -- if you were lucky all that had to be changed was the console I/O.  The CPM disks supplied by Digital Research were for an Intel MDS-800 system.  Almost no hobbyists had this expensive system.   With a 5" floppy disk (instead of 8" floppies) things were initially even more complicated.  Fortunately a group called Lifeboat Associates supplied a Northstar (hard sectored) floppy disk CPM system that got many of us stared in those days.  

I began with an SD Systems 5" soft sectored "SDOS" system and a Versafloppy-I FDC.  They supplied a disk diagnostic program (in source code) that allowed you to patch in your consol I/O directly on the disk (copied of course).  This was a very simple and basic way to get around the chicken & egg problem.  Over the years I have built all my systems utilizing a disk diagnostic program to implement and debug floppy and hard disk operating systems. 

Today things are much different. Today one would not dream of writing the basic BIOS with a cassette based assembler.  While you could do it on a currently functioning CPM system the easiest way to do things is to utilize a Windows based modern PC.  We do all the software editing etc. in something like Microsoft's Visual Studio, assemble it in a CPM emulator on the PC and when done, transfer the code to the S-100 system.  Please go here to read about this process before going any further.

Now there still remains one problem for somebody that does not have an up and running S-100 system of some sort -- getting that first custom disk operating system on to an 8" or 5" system track.  One way I see around this would be to format a 5" floppy disk on an old IBM PC in CPM format (various programs exist on the web), write a boot BIOS for this disk and use that disk to boot the S-100 system.  Alternatively do the same thing with an IDE disk/CF card (see below).   Unfortunately this is a lot of work and worse still,  a one time requirement.  If somebody can do all this please let me know I will add it to this site.

Meanwhile if there is somebody out there that absolutely is stuck I can try and "burn" a disk for them if they supply the BIOS software, and disk etc. -- no guarantees of course.

OK, for most of us the task rather will be implementing CPM3 on some kind of already functioning FDC/CPM S-100 system. Everything I have below can be utilized with most floppy disk controllers (FDC's).  However I will focus our example on our new Z80 based WD2793 FDC board which we call the ZFDC.  We will also do the same thing for the S-100 based IDE board.  Finally we will combine both into one fairly sophisticated BIOS.

Here is a diagram of the steps involved:-

CPM Boot Process
We will write software for all these steps.  Normally I have seen the process written from the bottom up. I think it is in fact easier to understand if we go from the top down.



SOFTWARE DOWNLOADS
All the software mentioned on this page can be downloaded from the links at the bottom of this page. 
They contain my most recent updates and may not correspond exactly with the text here. 

Also in this particular case, I am including the Altair CPM Simulator in the same folder so people can run the system immediately on windows.  Double click on altair80.exe to launch the program.   This will create a SIMH specific DOS box window.

At the "A>" prompt within that window type:-

do cpm3

After the carriage return you will see a completely self contained and functional CPM3 system. The box will look like this:-
 
Altair Signon
 
The simulator behaves like a ~50MHz  Z80 driven system.  For example to see the user 0 directory type:-

DIR

You will see the normal CPM3 directory listing, something like this:-

DIR listing

You can read up on CPM3 to run many of these programs. Other "well known" programs have been added as well. They are marked as CPM3 "System files" so they can be run from any drive and user group. 

For our needs here however, we will take advantage of the fact that the simulator has an large memory disk "I:" drive that we will normally use as our work disk. So typing "I:" CR will bring you to that drive.
You should see:
I:>

Within that drive will be already the relevant files to run the CPM3 files discussed below.  This makes it a one click process for beginners, however unfortunately the combined set of CPM files and CPM3 simulator is quite large (~4GB) because it contains many other CPM programs not used here.  For further CPM3 software downloads on other pages I will just supply the source code and you can splice in the simulator yourself as describe here.

OK lets get started. Starting form the top item in the above diagram:-


THE EPROM BOOT SECTOR LOADER.
This small piece of 8080/Z80 code resides in your boot monitor.  Because the amount of space in a ROM monitor is small (typically 4K) this code has to be simple and compact. Also because this same "Boot Loader" may be used to read in different operating systems (CPM 1.4, CPM2.2, CPM 3,  DOS etc) or different disk sizes/densities, we need a flexible way of reading in a different number of sectors from a disk. We do this in a two step process. We read in (always) the first sector to 80H in RAM, jump to that location which will have specific code on that disk for that operating system to determine how many further disk sectors are to be read.  These sectors are then read in by further code in the ROM monitor placing them at (for CPM),  100H in RAM.  The ROM monitor code then removes itself from the process and transfers all control to CPM starting at 100H in RAM.

The code at 100H is RAM is really a stripped down very compact CPM operating system (called CPMLDR.COM)  that's only function in life is to read is a single CPM file on the disk named CPM3.SYS.  It has to be a "proper" CPM operating system however because the CPM3.SYS file is a "normal" CPM file and can exist anywhere on the disk.   In a Non-Banked system this CPM3.SYS code is placed in the top of RAM. In a Banked system much of it is placed in another RAM bank - thus freeing up much of the TPA. More on that later.

We are almost home. The last step in the process is the CPM3.SYS code itself reads in a further CPM3 file called CCP.COM. This Console Command Processor  file has the code that links the operating system with the outside world (console, printer etc).  When this is done the CPM3.SYS code transfers control to the CCP.COM code and the A:> appears.

This my seem a convoluted way of doing things -- and to some extent it is, however what is really nice is that once you have the original CPMLDR working you can easily and quickly make changes to the operating system by placing a new CPM3.SYS file on the disk.  If you change the name (CPM always looks for exactly "CPM3.SYS") you can have different hardware arrangements stored on the same disk.

Note in the earlier versions of CPM (1.4, 2.2 etc) the process was much simpler. The first boot sector code caused the ROM monitor to load the  remaining sectors of CPM code in high memory and just jump to that location.  Any time a change was made to the BIOS however,  the whole system had to be rebuilt.

Now let us look at an example of the EPROM boot loader code.  My complete "MASTER.Z80" EEPROM code can be see here. The relevant BIOS loader code begins at ZFDC_BOOT:   This is the monitor I use with the S100Computers/N8VRAM Z80 CPU board.  It utilizes the simple command driven interface to read disk sectors into RAM utilizing the ZFDC board.
   
;---------------------- ZFDC FDC EEPROM BOOT LOADER -----------------------------------

ZFDC_BOOT:				;Cold Boot with ZFDC FDC Board
	OUT	RESET_ZFDC_PORT,A	;Do a ZFDC board hardware reset. Does not matter what is in [A]
	
	LD	A,STATUS_DELAY		;~0.5 second at 10 MHz 
	LD	BC,0			;Delay to allow board to setup hardware
WAIT_D:	DEC	B
	JR	NZ,WAIT_D		;Delay for ~0.5 seconds
	DEC	B			;Reset B to 0FFH
	DEC	C
	JR	NZ,WAIT_D
	DEC	A
	JR	NZ,WAIT_D
	
	IN	A,S100_DATA_B		;Check the board is there
	CP	A,CMD_HANDSHAKE		;Make sure we get HANDSHAKE byte back
	JP	NZ,ERR_NR		;If error, just abort 
	
	LD	A,CMD_HANDSHAKE		;Send another byte just to be sure.	
	OUT	S100_DATA_B,A		;This clears up ints on ZFDC board
	CALL	WAIT_FOR_ACK		;Wait to make sure all is well.
	OR	A,A
	JP	NZ,ERR_NR		;If error, just abort 
	
	LD	C,CMD_SET_FORMAT	;Send Set Disk Format to 8" SSSD DISK
	CALL	S100OUT
	LD	C,0			;Floppy Drive 0, (ZFDC Board expects a 0H, 1H, 2H or 3H)
	CALL	S100OUT			
	LD	C,STD8IBM		;ZFDC Board expects a Disk Format Table Number (0,1,2...13H)
	CALL	S100OUT			
	CALL	WAIT_FOR_ACK		;Return Z (and NO_ERRORS_FLAG in [A]), or NZ with error # in [A]
	JP	NZ,ERR_NR		;If error, just abort 
	
	LD	C,CMD_SET_DRIVE		;Send a "Set Drive CMD" to ZFDC board
	CALL	S100OUT
	LD	C,0			;Floppy Drive #, (ZFDC Board expects a 0H, 1H, 2H or 3H)
	CALL	S100OUT			
	CALL	WAIT_FOR_ACK		;Return Z (and NO_ERRORS_FLAG in [A]), or NZ with error # in [A]
	JP	NZ,ERR_NR		;If error, just abort 

					;Drive selected and ready to read sectors. Note this code 
					;is written to be compatible with the boot loader for the 
					;Versafloppy-II disk controller as well.
					
	LD	A,STDSDT		;SETUP FOR SD
	LD	(@COUNT),A		;STORE AS 26 SECTORS/TRACK
	
	XOR	A			;Setup Boot Sector read track
	LD	(@TRK),A
	INC	A
	LD	(@SCTR),A

	LD	HL,COLD                 ;Will load the boot sector to 80H in RAM
	LD	(@TADDR),HL

	CALL	ZFDC_READ_SECTOR

        JP      NZ,ERR_LD

        LD      HL,COLD                 ;Check the load went OK.
        LD      A,(HL)
        CP      31H                     ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
        JP      Z,COLD                  ;AS THE FIRST INSTRUCTION. IF OK JP 80H
        JP      ERR_LD1                 ;Boot Sector Data incorrect

ZFDC_READ_SECTOR:	                ;CORE code to read a sector with ZFDC board
	LD	C,CMD_SET_TRACK		;Set Track 
	CALL	S100OUT
	LD	A,(@TRK)
	LD	C,A
	CALL	S100OUT			;Send Selected track HEX number
	CALL	WAIT_FOR_ACK		;Return Z (and NO_ERRORS_FLAG in [A]), or NZ with error # in [A]
	JP	NZ,ERR_NR		;If error, just abort 
	
	LD	C,CMD_SET_SECTOR	;Set Sector # to side A (or for DS disks also side B)
	CALL	S100OUT
	LD	A,(@SCTR)
	LD	C,A
	CALL	S100OUT			;Send Selected sector HEX number
	CALL	WAIT_FOR_ACK		;Return Z (and NO_ERRORS_FLAG in [A]), or NZ with error # in [A]
	JP	NZ,ERR_NR		;If error, just abort 

	LD	C,CMD_SEEK_TRACK	;Later can let board do this	
	CALL	S100OUT
	CALL	WAIT_FOR_ACK		;Return Z (and NO_ERRORS_FLAG in [A]), or NZ with error # in [A]
	JP	NZ,ERR_NR		;If error, just abort 

	LD	C,CMD_READ_SECTOR	;Routine assumes required Drive Table,Drive,Side,Track, and sector are already sent to board
	CALL	S100OUT			;(Note [HL]-> Sector DMA address)	
	CALL	WAIT_FOR_ACK		;Wait for NO_ERRORS_FLAG to come back
	JP	NZ,ERR_NR		;If error, just abort 

	LD	HL,(@TADDR)		;Set DMA address
	LD	DE,(@SEC_SIZE)		;For CPM this will be 128 Byte sector(s)

RD_SEC:CALL	S100IN			;Note potential to lockup here & below (but unlightly)
	LD	(HL),A
	INC	HL
	DEC	DE
	LD	A,E
	OR	A,D
	JR	NZ,RD_SEC
	CALL	WAIT_FOR_ACK		;Return Z (and NO_ERRORS_FLAG in [A]), or NZ with error # in [A]
	RET


S100OUT:
	IN	A,S100_STATUS_B		;Send data to ZFDC output (arrive with character to be sent in C)
	BIT	DIRECTION_BIT,A		;Is ZFDC in output mode, if not wait
	JR	NZ,S100OUT
	BIT	DATA_OUT_RDY,A		;Has previous (if any) character been read.
	JR	Z,S100OUT		;Z if not yet ready
	LD	A,C
	OUT	S100_DATA_B,A
	RET

S100STAT:
	IN	A,S100_STATUS_B		;Check if ZFDC has any data for S-100 system
	BIT	DATA_IN_RDY,A		;Anything there ?
	RET	Z			;Return 0 if nothing
	XOR	A,A
	DEC	A			;Return NZ, & 0FFH in A if something there
	RET

S100IN:
	IN	A,S100_STATUS_B		;Check if ZFDC has any data for S-100 system
	BIT	DIRECTION_BIT,A		;Is ZFDC in input mode, if not wait
	JR	Z,S100IN		;If low then ZFDC board is still in input mode, wait
	BIT	DATA_IN_RDY,A
	JR	Z,S100IN
	IN	A,S100_DATA_A		;return with character in A
	RET

WAIT_FOR_ACK:				;Delay to wait for ZFDC to return data. There is a timeout of about 2 sec.
	PUSH	BC			;This can be increased if you are displaying debugging info on the ZFDC 
	PUSH	DE			;HEX LED display.
	LD	BC,0
	LD	E,STATUS_DELAY		;Timeout, (about 2 seconds)
WAIT_1:	IN	A,S100_STATUS_B		;Check if ZFDC has any data for S-100 system
	BIT	DIRECTION_BIT,A		;Is ZFDC in input mode
	JR	Z,WAIT_2		;if low then ZFDC is still in input mode
	CALL	S100STAT		;Wait until ZFDC Board sends something
	JR	Z,WAIT_2		
	CALL	S100IN			;Get returned Error # (Note this releases the SEND_DATA routine on the ZFDC board)
	CP	A,NO_ERRORS_FLAG	;Was SEND_OK/NO_ERRORS_FLAG sent back from ZFDC Board
	POP	DE			;Balance up stack
	POP	BC
	RET				;Return NZ if problem, Z if no problem
WAIT_2:	DEC	B
	JR	NZ,WAIT_1		;Try for ~2 seconds
	DEC	B			;Reset B to 0FFH
	DEC	C
	JR	NZ,WAIT_1
	DEC	B			;Reset B to 0FFH
	DEC	C
	DEC	E
	JR	NZ,WAIT_1
	XOR	A,A
	DEC	A
	POP	DE			;Balance up stack
	POP	BC
	RET				;Return NZ flag set if timeout AND 0FFH in [A]
As I explained above, we have just read in one sector to RAM at 80H.  The next piece of code is the EEPROM code to read in the next (in our case) 51 sectors from Track 0 and 1 on the 8" floppy disk.   The simple EEPROM code to do this is shown here:-
 
;	LOAD A NUMBER OF SECTORS	;Note this loader will be particularly slow since the sector
					;reads are not skewed. (Actually one rotation/sector)!
					
ZFDC_LOADER:				;Loader with ZFDC FDC Board
	CALL	ZFDC_READ_SECTOR
	JP	NZ,ERR_LD
	LD	C,'.'			;Show progress
	CALL	CO
	CALL	INCP			;Increment sector, track adjust NREC
	JR	NZ,ZFDC_LOADER 
	RET

;      INC SECTOR AND TRACK
INCP:  LD     HL,(@TADDR)
       LD     DE,(@SEC_SIZE)             ;128 or 512 byte sectors
INCP2: ADD    HL,DE
       LD     (@TADDR),HL
       LD     HL,@NREC
       DEC    (HL)
       RET    Z                          ;Return when we have done all sectors (~51)
       LD     HL,@SCTR
       INC    (HL)
       LD     A,(@COUNT)                 ;IS ONE TRACK DONE YET (Sec/track+1)
       INC    A
       CP     (HL)
       RET    NZ                         ;IF FULL Z, THEN GO TO NEXT TRACK
       LD     (HL),1                     ;SET SECTOR COUNT BACK TO 1
       INC    HL                         ;ASSUMES @TRK=SECTOR+1 IE 44H
       INC    (HL)
       OR     A                          ;MAKE SURE TO RETURN NZ
       RET
 
 
The key variable is @NREC. This is the number the first sector code placed in RAM (at 45H) to tell this module how many further sectors to read in.  Note how the ZFDC_LOADER routine just does a simple return when finished. Its again is up to the code from the first sector to then jump to 100H in RAM.



THE EPROM CPMLDR  LOADER.
Next we will write the CPM Loader program CPMLDR.COM. This is actually the hardest part of the software to write. We are is essence writing a primitive CPM3 BIOS. The good news is we don't worry about memory banking, and only have to take care of two major BIOS functions, reading sectors from (only) the boot disk and writing to the console. You don't even need console input -- though it is useful for debugging.
 
The complete Loader BIOS for the ZFDC board utilizing an 8" single density 128 byte sector disk (ZLDRBIOS.Z80) can be seen here.  Before we get into it, we need to discuss how CPM3 understands disk formats.


In CPM3 each disk has its own Disk Parameter Block  table.  Unfortunately these tables (DPB's) are fairly complex and rather than repeat everything here, you should read the Digital Research CPM3 System Guide mentioned above.  They contain byte and word values to define areas in RAM for sector skew translation, directory buffers and hash tables etc.   We will not worry about any of this now because Digital Research provides a series of Assembler macros that builds these tables automatically for you. 

For our 8" disk the DPB macro is:-

Floppy$DPB:    DPB    128,26,77,1024,64,2

 128   = Bytes per sector
   26   = Sectors per track
   77   = Tracks per disk
1024  = The allocation unit size (1K blocks for an 8" disk)
   64  =  Maximum number of directory entries on a disk
     2   = Number or tracks reserved for the CPM operating system.

Tracks start at 0,1,2,3,.. so tracks 0 & 1 are for the operating system. The disk directory starts on Track 2.
You will see this macro at the bottom of the above code listing.

Next we need another table which Digital Research calls a Disk Parameter Header  table or DPH.  This table is somewhat simpler and contains amongst other things a pointer to the above DPB table.   Again an assembler macro is supplied.  For our 8" disk the DPH macro is:-

DPH0:      DPH      SKEW6,Floppy$DPB,16,31

SKEW6           Is a pointer to another macro (described below) that describes how the sectors are numbered on a track.
Floppy#DPB   Is a word pointer to the above DPB for the 8" floppy disk.
16                  This is the maximum size in bytes of the disk checksum vector, lets skip for now
31                  This is the maximum size in bytes of the disk allocation vector, lets skip for now also and just use these values

The SKEW6 pointer, refers to a macro that describes the order of how sectors are arranged on a disk in terms of sector numbering.  If sectors were numbered 1,2,3,4..., sequential sector reading would be slow because once one sector has been read, processed and placed in RAM by the CPU the head has already moved along a few more sectors on the disk. The system would have to wait an almost complete rotation for next physical sector to come around.   By "skewing" the sectors this is avoided.  For example on a standard 8" IBM disk the order is:-

01H,07H,0DH,13H,19H,05H,0BH,11H,17H,03H,09H,07H,15H,02H,08H,0EH,14H,1AH,06H,0EH,12H,18H,04H,0AH,10H,16H
                 
Again to save you the hassle of figuring the order out Digital Research provided a macro:-

SKEW6:            SKEW 26,6,0

SKEW26       Is the total number of sectors per track for that disk
6                  This is the skip number for the skew.
0                  This is the number of the first sector on the disk. 

It turns out that the last number often causes problems. It is usually a 0 or 1.  If you set it to 0 then for all floppy disks in the actual disk "set sector" code before you send the data to the disk you must increment the value by one. This is because sectors on floppies are numbered 1,2,3.....    You can set the above to SKEW 26,6,1 and not do this, however its really important throughout all your BIOS code to be consistent. As we shall see the CF card/IDE drive BIOS'es number sectors 0,1,2,3 etc.   In all my code I use the 26,6,0 format.    I lost a lot of time in the past tracking a bug like this down! Your disk will appear fine initially but get messed up later when you go back and forth between disk formats.

OK we are almost there. There is still yet one more table.  This is the Disk Drive Table or DTBL

This one is simple.  CPM3 allows for up to 16 different drives.  The DPH table is just a list of pointers to each DPH for each drive. Any entries with no drive are set to 0.    We have only one drive here. The DTBL will be:-

@DTBL:       DW    DPH0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

When you look at the bottom of the code for the CPMLDR "BIOS" (ZLDRBIOS.Z80) you will see all of the above combined.
Now in order to have a functional basic operating system we need to splice in the Digital Research CPMLDR.REL file.  This a disk operating system file supplied by Digital Research that works with our basic custom BIOS.   Remember all we are trying to do here is read in the main CPM3.SYS file on the disk.  The CPMLDR.REL file is set to run at 100H in RAM.   It is constructed such that it expects immediately above it the Loader BIOS jump vectors exactly as they are at the start of ZLDRBIOS.ASM.

We use the CPM program LINK to splice the two sections together and write them out as one file CPMLDR.COM.

If you load this program with SID or ZSID and jump to 100H in RAM the above code should work and come back telling you it cannot locate the CPM3.SYS file on the disk. (Because we have not done it yet).

However before we get to that we have to do one other thing. We need to get some way to write the CPMLDR.COM file to tracks 0 and 1 of the floppy disk.

I have written the program ZSYSGEN.Z80 to do this.  It can be seen here.  Its essentially a reverse of the EPROM monitor code to write (rather than read) multiple sectors to the disk.

Finally we need to splice the ZSYSGEN and CPMLDR code together to yield one "standard"  CPM program which I call ZSYSGEN.COM This program on any standard IBM 8" floppy disk will place the above CPMLDR.com code on the system tracks.

The code is a little bit more complicate than I described above because it has the option of placing a Banked or Non-Banked CPMLDR.COM file on the disk. For now everything will be for a non-banked system.

All of the above assembly and splicing can be done automatically by running the ZSYSGEN.SUB file. This can be see here.



THE CPM3.SYS FILE.
This file is the heart of the operating system.  First we need to decide how we will layout our hardware. For our first system we will just have a Console and two IBM format 8" floppy disks A:& B:.

First we will work on the Floppy disk portion of the BIOS. This is contained in the file 8FL3.ASM.  The source code can be seen here. The BIOS contains essentially the same tables that I described above and uses the same assembler macros.  However there is one more new table structure it is called the Extended Disk Parameter Table (XDPH).   The XDPH is in fact nothing more than the above DPH table except that additional bytes and word parameters are placed immediately before and after the "regular" DPH table described above.  However the exact placement of these extra parameters is very critical.  Here is the XDPH table I use for drive A:
; EXTENDED DISK PARAMETER HEADER FOR DRIVE 0: (A:)
	DW	WRITE$SECTOR		;FD SEC WRITE ROUTINE
	DW	READ$SECTOR		;FD SEC READ ROUTINE
	DW	FLOPPY$LOGIN$0		;FLOPPY DISK "A:" LOGIN PROCEDURE
	DW	FLOPPY$INIT$0		;FLOPPY DISK "A:" DRIVE INITIALIZATION ROUTINE
	DB	0			;RELATIVE DRIVE 0 ON THIS CONTROLLER
	DB	STD8IBM			;MEDIA TYPE KNOWN SSSD 8" 
					;HI BIT SET : DRIVE NEEDS RECALIBRATING

DPH0:	DPH	SD128$trans,SDSS128$dpb,,
					;Bytes 0-24 used by DPH/CPM
	DW	128			;25, 128 Bytes per sector count
	DB	0			;27, Drive Hardware Select
You can see the normal DPH begins at DOH0:  Listed above it are two byte fields (unused here) that the software can use as flags for disk density/format etc.  CPM does not use them. Then immediately above them are four word pointers to disk the initialization, login, sector read and write routines.  These are the routines you must custom  write for each disk.  CPM also adds table values to the bottom of the DPH. In fact from the location DPH0: the next 24 bytes must never be changed by your custom BIOS. You can add any other flags or pointers you like below that. I use two in all my floppy disk BIOS'es.  one to hold the sector byte count for that disk format, the other the actual hardware select byte for that drive.

The relevance of all these flags and pointers being placed at these precise positions relative to DPH0: is that throughout CPM3,  all sector reads and writes will supply a pointer to the relevant drives DPH in the register pair [DE].  Using the following code:-

 	PUSHIX		;Save [IX]
	PUSH	D	;[DE]->[IX]
	POPIX		;get XDPH address for current requested drive to [ix]
we can utilize the useful Z80 [IX] register to obtain, compare, or change values within the XDPH table.  You will see a few examples where I have done this in this simple BIOS.  For example the bytes/sector for any disk is [IX+25].

Take a look over the 8FL3.ASM code to get comfortable with the above.   Don't worry about all the equates at the start,  most are not used. You should by now be familiar with the disk DPH, DPB and SKEW macros.

 
Next we need to look at the 8DRVTBL2.ASM file.  This one is simple.  It just contains the DTBL we described above.  However now we have a total of two disks whose DPH's are labeled DPH0 & DPH1 so we see:-

  @DTBL:        DTBL <DPH0,DPH1,0,0,0,0,0,0,0,0,0,0,0,0,0,0>

BTW, throughout these programs, Digital Research names all data pointers/variables  beginning with a "@'. All public routines  are labeled with an  '?'.  So ?PMSG is a globally available routine to print a message on the console.

Next we have CHARIO3.ASM. As you might expect from the name this file concerns itself with getting data to and from the Console (and printer).  There are elaborate mechanisms is CPM3 to set serial Baud rates etc. For this simple case just go to the bottom of the file and splice in code for your particular hardware.  The code there is actually more complicates than need be because I have options to redirect output to two printers and the like. Be sure you use the correct  I/O port equates for your hardware however.

Then we have 8BOOT3.ASM.  The only function of this file in our simple system is to place an initial signon message on the  screen when CPM3 signs on for the first time.   If you have various system/hardware configurations, the message at the bottom of the file is a useful place to put reminders.

So except for 3FL3.ASM  and CHARIO3.ASM , the changes you will have to make for any hardware system are fairly simple.
The other files BIOSKRNL.ASM,  SCB3.ASM,  MOVE3.ASM are not modified in this simple system.  

We assemble and link all of the above .ASM  files together to make our CPM3 custom BIOS file called BIOS3.SPR

Next we need one other supplied Digital Research file: BDOS.SPR. This file contains the code for the core disk operating (BDOS) system that Digital Research supplied.

We then utilize the special CPM3 program Digital Research supplied called GENCPM.COM. This program will take the above BDIOS3.SPR and BIOS3.SPR files and combine them to make our final CPM3.SYS file.  GENCPM is an interactive program. It will ask you a number of questions about how exactly you would like your custom CPM3 operating system to function.  Things like do you want long error messages, which disk you want as the boot/primary disk, how much space to set aside for directory buffers etc.

Fortunately Digital Research provided good examples.  The input for the GENCPM program can be read from a GENCPM.DAT file. This saves you typing in the same values each time.  I provide a sample GENCPM.DAT here.

The above process actually appear here more complex than it actually is.  I have written the following CPM submit file that will do all of the above with a one line entry the Windows Altair CPM emulator box on the I: Drive.
Just type:-

submit 8MAKECPM

The final CPM3.SYS file (see the 8MAKECPM.SUB file here) should appear in your windows directory from where you launched Altair.com

Here is a listing of that 8MAKECPM.SUB file:-
;r RMAC.COM
;r LINK.COM
;r GENCPM.COM
;
r GENCPM.DAT
;
r Z80.LIB
r CPM3.LIB
r MODEBAUD.LIB
;
r BIOSKRNL.ASM
r BDOS3.SPR
r SCB3.ASM
r MOVE3.ASM
;
r CHARIO3.ASM
r 8boot3.asm
r 8drvtbl3.asm
r 8FL3.ASM
;
RMAC BIOSKRNL.ASM
RMAC SCB3.ASM
RMAC MOVE3.ASM
;
RMAC CHARIO3.ASM
RMAC 8BOOT3.ASM
RMAC 8DRVTBL3.ASM
RMAC 8FL3.ASM
;
LINK BIOS3[B]=BIOSKRNL,SCB3,8BOOT3,CHARIO3,MOVE3,8DRVTBL3,8FL3
;
;Note GENCPM.DAT holds previous values
;This will be a Non-Banked version of CPM3
;Note take care if you remove "auto" and do it yourself 
;to have reasonable values that will not overload RAM in this
;non-banked system
;
gencpm auto
w cpm3.sys B
;
; Now copy the cpm3.sys to your 8" bootable floppy. 
; Make sure the file ccp.com is there as well. 
; Use ZSYSGEN.COM to write a system on the disk.
You next need to transfer the above CPM3.SYS file to an IBM 8" floppy disk.  See here as one way to do this.

Finally you need to also transfer to this same disk  the CPM program CCP.COM.

With your CPMLDR code already on the disks system tracks as describe above and the two files CPM3.SYS and CCP.COM listed in the disks directory you are ready to boot CPM3!

If it does not work, first from an old CPM 2.2 system try loading the CPMLDR file into RAM with SID. Switch disks and go to 100H.   The system should boot. If it does there is a problem with your boot loader. 

If you still have problems then use the approach of sprinkling your BIOS code with Console  character outputs (direct to hardware, not via CPM) at various points to see where the problem lies.  One nice thing about the ZFDC board is you can always see what drive, track and sector is being addressed.   Directory reads for example are on Track 2.  The boot loader should start on Track 0 and step along all the way up to Track 1, sector 1AH.

Note all of the above assumes that your ZFDC board and disk drives work 100% reliable in ALL tests of the ZFDCDIAG.COM
program.  Don't even think about all the above code unless this is so! 



A BANKED SIMPLE FLOPPY VERSION OF CPM3.
Next we will move up to a banked version of CPM3 in the same system.   As I said above, CPM3 comes in two flavors. A "Non-Banked" system where up to 64K of RAM  can be utilized to run the operation system and application(s).  The second much more powerful approach is a "Banked" system. This utilizes two or more S-100 memory cards to switch in or out various blocks of memory and yet still remain within the 64K memory addressing capability of the Z80. Typically the bank is a 16 or 32K block of RAM that transitions in or out of the CPU's 16 bit address space.   These banks can be port selected utilizing the S-100 phantom line and/or by activating and inactivation a port on certain S100 RAM boards which by using onboard 'bank switching" hardware, could utilize up to (in theory) many megabytes of RAM. Typically 128K or 256K RAM systems were used.   This allowed for a very fast and sophisticated system. 

It should be noted that this BANKED system absolutely requires that the Z80 has hardware that can switch in and out a portion of its 64K address space with other RAM boards.  There were a number of ways this was done in hardware.  The Cromemco and Godbout systems utilized an IO port to switch RAM boards.  Intersystem's and our own S100Computes/N8VEM Z80 board, use on-board Z80 CPU board hardware to extend the addressing range of the Z80. 
The two types of approaches can be diagramed as follows:-

Extended Addressing modes

They both behave the same in terms of software. Digital Research referred to these "extra" memory blocks of RAM as "Banks".  In a typical CPM3 Banked system there are usually two, sometimes 3 banks.  Where these banks "split" in the Z80's 64K address space varies with the hardware.  With our S100Computers Z80 board this is completely determined by software. Not so typically, in I/O port driven systems. A common split is at the 8000H or C000H boundary. 
Here is a typical Bank CPM3 setup:-
 
CPM3 Memory layout
 
In a banked CPM3 system, the Monitor/EPROM Boot loads the CPM3
CPMLDR.COM  from the disk system tracks into RAM at 100H exactly as described above for the non-banked system.   However once control is passed to the CPMLDR at 100H in RAM,  things go differently.  The first thing the loader does in a banked system is open up an alternative bank of RAM above/outside the Z80's 64K address space.  As I said above this is quite hardware dependent and varies from system to system.

In our S100Computers Z80 board's case we will move the RAM contents from 0H to 7FFFH up to 10000H to 17FFFH.  In Digital Research terminology, we call the RAM from 10000H-17FFFH Bank 0.  We call the "old" RAM at 0-7FFFH Bank 1.  Using the boards hardware we then flip banks  so the RAM contents change as is shown here:-
 
Bank Switching


It's very important to understand that the Z80 itself does not know anything has happened.  It all the time sees one large block of 64K RAM. In fact while it thinks it is operating on RAM 0  to ~4K it is in fact using RAM from 1000H upwards ~4K.

The CPMLDR then loads in the main CPM3.SYS file from the disk as we described above for the non-banked system but this time instead of dumping everything in high memory,  it distributes the contents both in high memory (a small portion) and the rest in our Bank 1 memory (the major portion of code).   How CPM does this we will discuss in a moment.  What part goes where is the tricky part of writing a good CPM3 BIOS.  In all the BIOS code we will look at you will see chunks assigned to "Common Memory Segments"
(CSEG's) or "Data Memory Segments" 
(DSEG's).  CSEG portions of the BIOS code always reside above and outside the Banked switching region of RAM in high memory.  They never change, any addresses are absolute and can always count on being there.   In other words the code behaves like Non-Banked BIOS code.    DSEG BIOS code on the other hand resides in Bank 0 RAM (or other Banks). Nothing special about the code itself within this segment either. 

What is special is when you jump back and forth between code in DSEG RAM and CSEG RAM.  Before you make a jump say from code in DSEG to a CSEG, you need to save the current bank number, switch banks so Bank1 is contiguous with the CSEG code above the bank switching area in high memory and then jump from that DSEG (now in the contiguous 64K RAM space) to CSEG code in high RAM.  This is necessary because remember the Z80 never knows banks are switched. If the code in CSEG just jumped to a region below the bank boundary it would not find the CSEG code there.  (It is in fact, way up above 10000H in RAM).

Here is a diagram of the process:-

The CPM banking Process
 

Let us look at a small code snipped from the BIOS Floppy Disk sector read routine.  Most of the BIOS code will be in banked RAM and so DSEG.  However when we get to the part where we are actually reading in data bytes from the FDC and placing them in RAM (pointed to by [HL]) we need to be sure [HL] is actually pointing to RAM in the TPA (Bank 1) and so that code need to be in the Z80's 64K of contiguous RAM

We store the current Bank number on the stack
LDA @CBNK ;get current bank
PUSH PSW
Load the destination bank (in this case bank1)
LDA @DBNK ;Get Destination Bank. MUST HAVE THIS CODE IN COMMON
Switch banks
CALL ?BNKSL ;NOW DMA ADDRESS IS AT THE CORRECT BANK
And transfer the data to the correct location.
Further down we see we reverse the process.
	DSEG				;<---- Code in Data Segment (Banked RAM)

READ$SECTOR:
	PUSH	D			;Save it just in case CPM uses it later
	PUSHIX				;Save [IX]
	PUSH	D			;[DE]->[IX]
	POPIX				;get XDPH address for current requested drive to [ix]
	
	MVI	C,CMD$SET$DRIVE		;<<< Set Drive. Will just return if current drive
	CALL	S100OUT			;We need this each time because commands like PIP do not Login a   
	LDX	C,DRIVE$SELECT		;[C] <- (IX + DRIVE$SELECT). Floppy Drive #
	CALL	S100OUT			
	CALL	WAIT$FOR$ACK		;Return Z (and NO$ERRORS$FLAG in [A]), or NZ with error # in [A]
	JNZ	READ$ERROR

	MVI	C,CMD$SET$TRACK		;<<< Set Track 
	CALL	S100OUT
	LDA	@TRK
	MOV	C,A
	CALL	S100OUT			;Send Selected track HEX number
	CALL	WAIT$FOR$ACK		;Wait for NO$ERRORS$FLAG to come back
	JNZ	READ$ERROR
	
	MVI	C,CMD$SET$SECTOR	;<<< Set Sector 
	CALL	S100OUT
	LDA	@SECT			;Note:- SD128$trans:	skew	26,6,0<---- Start with sec# 0
	INR	A			;Disk sectors 1...MAXSEC
	MOV	C,A
	CALL	S100OUT			;Send Selected sector HEX number
	CALL	WAIT$FOR$ACK		;Wait for NO$ERRORS$FLAG to come back
	JNZ	READ$ERROR

	MVI	C,CMD$SEEK$TRACK	;<<< Seek to Track	
	CALL	S100OUT
	CALL	WAIT$FOR$ACK		;Return Z (and NO$ERRORS$FLAG in [A]), or NZ with error # in [A]
	JNZ	READ$ERROR

	MVI	C,CMD$READ$SECTOR	;Routine assumes required Drive Table,Drive,Side,Track, and sector 
	CALL	S100OUT			;(Note [HL]-> Sector DMA address)	
	CALL	WAIT$FOR$ACK		;Wait for NO$ERRORS$FLAG to come back
	JNZ	READ$ERROR

	LHLD    @DMA			;Get DMA address
	LDX	E,BYTE$COUNT		;[E] <- (IX + BYTE$COUNT). Sector size (128 or 512)
	LDX	D,BYTE$COUNT+1		;[D] <- (IX + BYTE$COUNT+1). Sector size
	
	JMP	ADJ$BANK2
	
	CSEG
;================================<<<<<< SWITCHING BANKS >>>>>>>>>>>
ADJ$BANK2:
	LDA	@CBNK			;get current bank
	PUSH	PSW
	LDA	@DBNK			;Get Destination Bank. MUST HAVE THIS CODE IN COMMON
	CALL	?BNKSL			;NOW DMA ADDRESS IS AT THE CORRECT BANK
	
RD$SEC:	
	LXI	B,0400H			;Put in a timeout count (Loop for status read at most 256X4 times)
RD$SEC1:
	DCX	B			;Dec BC
	MOV	A,C			;If you find a lot of sector R/W timeouts errors coming back 
	ORA	B			;(Error code 0FFH), increase this value
	JNZ	RD$SEC2			;Will wait 400H times before timing out
	
	POP	PSW
	CALL	?BNKSL
	MVI	A,TIMEOUT$ERROR		;Send Timeout error
	JMP	READ$ERROR		;<--- Note JMP back to DSEG bank!
	
					;We cannot use S100IN here since we are no longer in the DSEG bank
RD$SEC2:IN	S100$STATUS$B		;Check if ZFDC has any data for S-100 system
	BIT	DIRECTION$BIT,A		;Is ZFDC in input mode, if not wait.
	JRZ	RD$SEC1			;If low then ZFDC board is still in input mode, wait
	BIT	DATA$IN$RDY,A		;Is there a character available
	JRZ	RD$SEC1
	
	IN	S100$DATA$A		;Input byte in [A] from ZFDC port
	MOV	M,A			;Store it at [@DMA]
	INX	H			;[HL++] for [DE--] bytes in sector
	DCX	D
	MOV	A,E
	ORA	D
	JRNZ	RD$SEC			;Next Byte, reset timeout count

	POP	PSW
	CALL	?BNKSL
	JMP	CHECK$RD
;================================<<<<<< SWITCHING BANKS >>>>>>>>>>>
	DSEG
	
CHECK$RD:
	CALL	WAIT$FOR$ACK		;Return Z (and NO$ERRORS$FLAG in [A]), or NZ with error # in [A]
	JNZ	READ$ERROR
	POPIX				;Return with original IX
	POP	D			;and DE
	RET				;Ret Z (from WAIT$FOR$ACK)

This bank switching process may seem a bit confusing at first but you quickly get used to it.   Remember any time you wish to jump across from a DSEG code to a piece of CSEG code you must change/check segments first. As we shall see if you have a solid Non-banked CPM3 BIOS working,  its not too hard to hammer it into a Banked mode of operation.   More on that in a moment.

Before we get to that however we need to go back to one thing again, CPM's tables, specifically the
DPB's, DPH's and XDPH's.

Simply stated, all
DPH and SKEW tables can be and usually are placed in Banked memory (DSEG's) but all DPB tables must reside in CSEG memory.  As you build your Banked BIOS start with everything in CSEG's and gradually move as much as  you can across to banked (DSEG) RAM thereby freeing up the TPA. One very common mistake of beginners is to have the DPB table in the DSEG/Banked memory.  This can lead to much frustration and debugging!

The absolute kernel code of switching banks and moving blocks of code from one bank to another in CPM3 is all completely contained within a module called
MOVE3.ASM.  This file contains code to move 128 byte blocks of memory data either within a CSEG or DSEG area (Intra-Bank Moves) or 128 bytes from one bank to another (Inter-Bank Moves).   It also contains code to do the hardware switching of banks (?BANK:).   This MOVE.ASM module is perhaps the hardest piece of code to understand in the whole of the CPM3 BIOS. It absolutely has to be reliable. Unfortunately it is also very hardware dependent. The example I have below is for our S100Computers (or Intersystem's) Z80 CPU boards.  In this case we use the S-100 bus extended memory address space and one of the 4MG Static RAM boards.  If you use the I/O port mode of bank switching (e.g. Cromemco or CompuPro etc) you will have to rewrite the kernel of the code.


Writing Banked CPM3 BIOS Code.
The process of writing BIOS modules for a banked BIOS version of CPM3 is essentially the same as describe above for the non-banked version -- with one very important exception - each .ASM file has the "
BANKED" equate set to "TRUE" at the start of each file. This includes the Digital Research supplied file BIOSKRNL.ASM.   In all the files except the memory banks/RAM move  and Floppy disk related BIOS code files (8FL3.ASM and MOVE3.ASM) any other changes are minor.

Please look over careful the file 8FL3.ASM. It is still the same basic code as for the non-banked version, but it incorporates the bank switching functions discussed above. Try and really understand this file before going any further.

There is one further major difference between the Banked and Non-Banked versions of CPM3. When we come to linking all the BIOS modules together to make our reloadable combined BIOS file (for GENCPM) for a Banked version of CPM we  actually have two components of the Digital Research supplied core BIOS and BDOS files. One set is for the "Resident" (CSEG) portion of CPM3. The other is for the "Banked" (DSEG) code.  The input files for the Banked and Non-Banked therefore look like this:-

  Non-BANKED CPM3    Banked CPM3 
  BIOS3.SPR    BNKBIOS3.SPR 
  BDOS3.SPR    RESBDOS3.SPR 
      BNKBDOS.SPR 

So the 8MAKECPM.SUB to link everything together is a little different.  Here is the 8MAKECPM submit file for our simple dual 8" floppy disk system of a banked version of CPM3:-
  
;r RMAC.COM
;r LINK.COM
;r GENCPM.COM
;
;
r GENCPM.DAT
;
r Z80.LIB
r CPM3.LIB
r MODEBAUD.LIB
;
r BIOSKRNL.ASM
r BNKBDOS3.SPR
r RESBDOS3.SPR
;
r SCB3.ASM
r CHARIO3.ASM
r MOVE3.ASM
;
r 8boot3.asm
r 8drvtbl3.asm
r 8FL3.ASM
;
RMAC BIOSKRNL.ASM
RMAC SCB3.ASM
RMAC CHARIO3.ASM
RMAC MOVE3.ASM
;
RMAC 8BOOT3.ASM
RMAC 8DRVTBL3.ASM
RMAC 8FL3.ASM
;
;A: & B: are 8" Floppy disks.  
;
LINK BNKBIOS3[B]=BIOSKRNL,SCB3,8BOOT3,CHARIO3,MOVE3,8DRVTBL3,8FL3
;
;Note GENCPM.DAT holds previous values
;This will be a Banked version. 
;
gencpm auto
;
w cpm3.sys B
;
; Note this version is BANKED and for a ZFDC FDC Board
; Now copy the cpm3.sys to your 8" bootable floppy. 
; Make sure the file ccp.com is there as well. 
; Use ZSYSGEN.COM to write a system on the disk.
    
From here on the process for the Banked and Non-banked versions of CPM3 are identical.
You next need to transfer the above CPM3.SYS file to an IBM 8" floppy disk as we discussed above for the non banked version.

You need to also transfer to this same disk  the CPM program CCP.COM. (The CCP.COM file is the same for both versions)

With your CPMLDR code already on the disks system tracks as describe above and the two files CPM3.SYS and CCP.COM listed in the disks directory you are ready to boot the banked version of CPM3!

If it does not work, as before first from an old CPM 2.2 system try loading the CPMLDR file into RAM with SID. Switch disks and go to 100H.   The system should boot. If it does there is a problem with your banked version of the boot loader. 

If you still have problems then again use the approach of sprinkling your BIOS code with Console  character outputs (direct to hardware, not via CPM) at various points to see where the problem lies. 

Here are two short videos demonstrating the booting of the Non-Banked and Banked versions of CPM3.

Non-Banked Version of CPM3
  
     
Banked Version of CPM3


The links below will contain the most recent versions of this software.
Note, it may change over time and may not correlate exactly with the text in the article above.

MOST CURRENT FLOPPY ZSYSGEN SOFTWARE (03/16/2011)
MOST CURRENT 8MAKECPM (for AB Floppy drives, Non-Banked CPM 3 system)  (03/16/2011)
MOST CURRENT 8MAKECPM (for ABCD Floppy drives, Non-Banked CPM 3 system)  (03/16/2011)
MOST CURRENT 8MAKECPM (for AB Floppy drives, Banked CPM 3 system)  (03/06/2011)
MOST CURRENT 8MAKECPM (for ABCD Floppy drives, Banked CPM 3 system)  (03/06/2011)
Common Digital Research Programs to Assemble these Systems (03/06/2011)
MOST CURRENT MASTER.Z80  (03/16/2011)

 
Also see the corresponding CPM3 system files for the S-100 IDE Board here.


This page was last modified on 03/16/2011